import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:aichat_flutter_wallet/module_common/network/web3_js_call.dart';
import 'package:eip55/eip55.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:lib_database/src/bean.dart';
import 'package:lib_database/src/config.dart';
import './chaincore.dart';
import 'package:lib_util/utils/utils.dart';

class EthChain extends ChainCore {
  EthChain._privateConstructor();

  static final EthChain _instance = EthChain._privateConstructor();

  factory EthChain() {
    return _instance;
  }

  // 获取 Erc20 Usdt 价格
  Future<String> getErcMainTokenPriceU(String chainId, String? rpc) async {
    Completer<String> completer = Completer();
    return generateFuture(
        Web3JsCall().jsErc20MainPriceU(
          chainId,
          rpc,
          JsCallback(callback: (result) {
            completer.complete(result);
          }, errorCallback: (error) {
            completer.completeError(error);
          }),
        ),
        completer,
        timeoutSeconds: 5000);
  }

  // 获取 Erc20 区块高度 价格
  Future<int> getERC20BlockHeightByRpc(String rpc) async {
    Completer<int> completer = Completer();
    return generateFuture(
        Web3JsCall().getERC20BlockHeightByRpc(
          rpc,
          JsCallback(callback: (result) {
            print('result  $result');
            completer.complete(result);
          }, errorCallback: (error) {
            print('error======= $error');
            completer.completeError(error);
          }),
        ),
        completer,
        timeoutSeconds: 5000);
  }

  // 获取 Erc20 Usdt 价格
  Future<String> getErcOtherTokenPriceU({
    required String chainId,
    String? rpc,
    required String tokenFromAddress,
    required String tokenToAddress,
  }) async {
    Completer<String> completer = Completer();
    return generateFuture(
        //required String chainId, String? rpc, required String tokenFromAddress, required String tokenToAddress,required JsCallback callback
        Web3JsCall().getTokenCurrencyPriceOtherTokenPriceU(
          rpc: rpc,
          chainId: chainId,
          tokenFromAddress: tokenFromAddress,
          tokenToAddress: tokenToAddress,
          callback: JsCallback(callback: (result) {
            completer.complete(result);
          }, errorCallback: (error) {
            completer.completeError(error);
          }),
        ),
        completer,
        timeoutSeconds: 5000);
  }

  // 获取 Erc20 Gas
  Future<Map<String, dynamic>?> getErcGasFee({
    required String type,
    required String rpc,
    required String from,
    required String token,
  }) async {
    Completer<Map<String, dynamic>?> completer = Completer();
    return generateFuture(
        //required String chainId, String? rpc, required String tokenFromAddress, required String tokenToAddress,required JsCallback callback
        Web3JsCall().jsErc20GasFee(
          type,
          rpc,
          from,
          token,
          JsCallback(callback: (result) {
            completer.complete(result);
          }, errorCallback: (error) {
            completer.completeError(error);
          }),
        ),
        completer,
        timeoutSeconds: 10);
  }

  // 获取 Erc20 代币合约 信息
  Future<Map<String, dynamic>?> getErc20TokenContractInfo(String chainId, String contractAddress, String rpc) async {
    Completer<Map<String, dynamic>?> completer = Completer();
    return generateFuture(
        Web3JsCall().jsErc20TokenContractInfo(
            chainId,
            contractAddress,
            rpc,
            JsCallback(callback: (result) {
              completer.complete(result);
            }, errorCallback: (error) {
              completer.completeError(error);
            }),
            true),
        completer,
        timeoutSeconds: 5000);
  }

  Future<String> signMessage(String pk, String message) async {
    if (!await localAuth()) {
      return Future.error('Authentication failed');
    }
    return EthSigUtil.signPersonalMessage(message: Uint8List.fromList(utf8.encode(message)), privateKey: pk);
  }

  Future<String> signTypedMessage(String pk, String message) async {
    if (!await localAuth()) {
      return Future.error('Authentication failed');
    }
    return EthSigUtil.signTypedData(jsonData: message, privateKey: pk, version: TypedDataVersion.V4);
  }

  Future<String> sendTransaction(String pk, ETHTransaction transaction, String rpc,
      {bool waitForReceipt = false, bool showError = true}) async {
    Completer<String> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthSendTransaction(
            pk, transaction, rpc, waitForReceipt, generateCallback(completer, showError: showError)),
        completer,
        timeoutSeconds: 100);
  }

  Future<String> sendTransaction2(
      String type, String contractAddress, String pk, ETHTransaction transaction, String rpc,
      {bool waitForReceipt = false, bool showError = true}) async {
    Completer<String> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthSendTransaction2(type, contractAddress, pk, transaction, rpc, waitForReceipt,
            generateCallback(completer, showError: showError)),
        completer,
        timeoutSeconds: 100);
  }

  Future<Map<String, dynamic>> SendTx(
      String type, String contractAddress, ETHTransaction transaction, int chainId, String rpc,
      {bool showError = true}) async {
    Completer<Map<String, dynamic>> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthSendTx(
            type, contractAddress, transaction, chainId, rpc, generateCallback(completer, showError: showError)),
        completer,
        timeoutSeconds: 15);
  }

  Future<String> SignData(String sginStr, {bool showError = false}) async {
    Completer<String> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthSignData(sginStr, generateCallback(completer, showError: showError)), completer,
        timeoutSeconds: 100);
  }

  Future<String> SingSend(Map<String, dynamic> transaction, String sginStr, int chainId, String rpc,
      {bool waitForReceipt = true, bool showError = false}) async {
    Completer<String> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthSingSend(
            transaction, sginStr, chainId, rpc, waitForReceipt, generateCallback(completer, showError: showError)),
        completer,
        timeoutSeconds: 100);
  }

  Future<ETHGasFee> ethEstimateGasFee(String rpc, String transaction) {
    Completer<ETHGasFee> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEstimateGasFee(
            rpc,
            transaction,
            JsCallback(callback: (result) {
              completer.complete(ETHGasFee.fromJson(result as Map<String, dynamic>));
            }, errorCallback: (error) {
              completer.completeError(error);
            })),
        completer);
  }

  Future<ETHTransaction> buildSendTokenTransaction(
      {required String type,
      required String rpc,
      required String from,
      required String to,
      required String token,
      required String amount,
      String? tokenId,
      String? gas,
      String? gasPrice}) {
    Config.logger.d(
        'buildSendTokenTransaction:type:$type,rpc:$rpc,from:$from,to:$to,token:$token,amount:$amount,tokenId:$tokenId,gas:$gas,gasPrice:$gasPrice');

    Completer<ETHTransaction> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthBuildSendTokenTransaction(
            type: type,
            rpc: rpc,
            from: from,
            to: to,
            token: token,
            amount: amount,
            tokenId: tokenId,
            gas: gas,
            gasPrice: gasPrice,
            callback: JsCallback(
                showError: false,
                callback: (result) {
                  completer.complete(ETHTransaction.fromJson(result as Map<String, dynamic>));
                },
                errorCallback: (error) {
                  completer.completeError(error);
                })),
        completer);
  }

  Future<ETHTransaction> buildEthBuildBatchSendNFTTransaction(
      {required String type,
      required String rpc,
      required String batchContract,
      required String from,
      required String to,
      required String token,
      required List<String> tokenIds,
      required List<int> amounts,
      String? gas,
      String? gasPrice}) {
    Completer<ETHTransaction> completer = Completer();
    return generateFuture(
        Web3JsCall().jsEthBuildBatchSendNFTTransaction(
            type: type,
            rpc: rpc,
            batchContract: batchContract,
            from: from,
            to: to,
            token: token,
            tokenIds: tokenIds,
            amounts: amounts,
            callback: JsCallback(callback: (result) {
              completer.complete(ETHTransaction.fromJson(result as Map<String, dynamic>));
            }, errorCallback: (error) {
              completer.completeError(error);
            })),
        completer);
  }

  Future<ETHTransaction> buildAnySwapTransaction({
    required String rpc,
    required String token,
    required String contract,
    required String fromAddress,
    required String crossAddress,
    required String amount,
  }) {
    Completer<ETHTransaction> completer = Completer();

    return generateFuture(
        Web3JsCall().jsBuildAnySwapTransaction(
            rpc: rpc,
            token: token,
            contract: contract,
            fromAddress: fromAddress,
            crossAddress: crossAddress,
            amount: amount,
            callback: JsCallback(callback: (result) {
              completer.complete(ETHTransaction.fromJson(result as Map<String, dynamic>));
            }, errorCallback: (error) {
              completer.completeError(error);
            })),
        completer);
  }

  Future<ETHTransaction> buildOmpBridgeTransaction({
    required String rpc,
    required String contract,
    required String fromAddress,
    required String crossAddress,
    required String amount,
  }) {
    Completer<ETHTransaction> completer = Completer();

    return generateFuture(
        Web3JsCall().jsEthBuildOmpBridgeTransaction(
            rpc: rpc,
            contract: contract,
            fromAddress: fromAddress,
            crossAddress: crossAddress,
            amount: amount,
            callback: JsCallback(callback: (result) {
              completer.complete(ETHTransaction.fromJson(result as Map<String, dynamic>));
            }, errorCallback: (error) {
              completer.completeError(error);
            })),
        completer);
  }

  Future<CrossInfoBean> checkAnySwapInfo(
      {required String sellRpc,
      required String buyRpc,
      required String sellToken,
      required String buyToken,
      required String anySwapData,
      required String walletAddress}) {
    Completer<CrossInfoBean> completer = Completer();

    return generateFuture(
        Web3JsCall().jsCheckAnySwapInfo(
            sellRpc: sellRpc,
            buyRpc: buyRpc,
            sellToken: sellToken,
            buyToken: buyToken,
            anySwapData: anySwapData,
            walletAddress: walletAddress,
            callback: JsCallback(callback: (result) {
              completer.complete(CrossInfoBean.fromJson(result as Map<String, dynamic>));
            }, errorCallback: (error) {
              completer.completeError(error);
            })),
        completer);
  }
}

class ETHTransaction {
  String? from;
  String? to;
  String? value;
  String? gas;
  String? gasPrice;
  String? data;
  int? nonce;

  ETHTransaction({
    this.from,
    this.to,
    this.value,
    this.gas,
    this.gasPrice,
    this.data,
    this.nonce,
  });

  ETHTransaction.fromJson(Map<String, dynamic> json) {
    from = json['from'];
    to = json['to'];
    value = json['value'];
    gas = json['gas'].toString();
    gasPrice = json['gasPrice'].toString();
    data = json['data'];
    nonce = json['nonce'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> result = <String, dynamic>{};
    if (null != from) result['from'] = from;
    if (null != to) result['to'] = to;
    if (null != value) result['value'] = value;
    if (null != gas) result['gas'] = gas;
    if (null != gasPrice) result['gasPrice'] = gasPrice;
    if (null != data) result['data'] = data;
    if (null != nonce) result['nonce'] = nonce;
    return result;
  }

  String calGasFee() {
    if (null == gas || null == gasPrice) {
      return '';
    }

    BigInt gasPriceInt = BigInt.parse(gasPrice!);
    BigInt gasInt = BigInt.parse(gas!);

    return Utils.formatStringBalance((gasInt * gasPriceInt / BigInt.from(10).pow(18)).toString(),
        fractionDigits: Config.balanceFixedNum);
  }

  String? decodeApproveToAddress() {
    if (getMethods() != TransactionFunc.approve.name) {
      return null;
    }

    try {
      String a = data!.substring(10, 10 + 64);
      return toChecksumAddress('0x${a.substring(a.length - 40)}');
    } catch (e) {
      Config.logger.e(e);
      return null;
    }
  }

  String? decodeTransferToAddress() {
    if (getMethods() != TransactionFunc.transfer.name) {
      return null;
    }

    try {
      String a = data!.substring(10, 10 + 64);
      return toChecksumAddress('0x${a.substring(a.length - 40)}');
    } catch (e) {
      Config.logger.e(e);
      return null;
    }
  }

  String getMethods() {
    if (Utils.isEmpty(data) || data!.length < 10) {
      return '';
    }
    String methods = data!.substring(0, 10);
    switch (methods) {
      case '0x5c11d795':
        return TransactionFunc.swapExactTokensForTokensSupportingFeeOnTransferTokens.name;
      case '0x38ed1739':
        return TransactionFunc.swapExactTokensForTokens.name;
      case '0xa9059cbb':
        return TransactionFunc.transfer.name;
      case '0x095ea7b3':
        return TransactionFunc.approve.name;
      case '0xa22cb465':
        return TransactionFunc.setApproveForAll.name;
      case '0xe8e33700':
        return TransactionFunc.addLiquidity.name;
      default:
        return methods;
    }
  }
}

class ETHGasFee {
  String? gas;
  String? gasPrice;

  ETHGasFee.fromJson(Map<String, dynamic> json) {
    gas = json['gas'].toString();
    gasPrice = json['gasPrice'].toString();
  }
}

class ERC20Token {
  int? chainId;
  String? address;
  int? decimals;
  String? symbol;
  String? name;
  String? projectLink;
  bool? isToken;
  bool? isNative;

  ERC20Token(
      this.chainId, this.address, this.decimals, this.symbol, this.name, this.isNative, this.isToken, this.projectLink);

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> map = {
      "chainId": this.chainId,
      "name": this.name.toString(),
      "address": this.address.toString(),
      "decimals": this.decimals,
      "symbol": this.symbol.toString(),
      "projectLink": this.projectLink != null ? this.projectLink.toString() : '',
      "isNative": this.isNative ?? false,
      "isToken": this.isToken ?? true,
    };
    return map;
  }
}
